package nkw;

import java.awt.Color;
import robocode.Robot;

/**
 * Base configuration and functionality for custom robots.
 * 
 * @author Noah Woodden <noah.woodden@gmail.com>
 */
public class RobotBase extends Robot {

  /**
   * Width of robot.
   */
  private double robotWidth;

  /**
   * Height of robot.
   */
  private double robotHeight;

  /**
   * Height of battle field.
   */
  private double battleFieldHeight;

  /**
   * Width of battle field.
   */
  private double battleFieldWidth;

  /**
   * Setup for general robot configuration.
   */
  public void robotSetup() {
    // Get game specifications.
    this.robotWidth = this.getWidth();
    this.robotHeight = this.getHeight();
    this.battleFieldWidth = this.getBattleFieldWidth();
    this.battleFieldHeight = this.getBattleFieldHeight();

    // Set colors.
    this.setBodyColor(Color.black);
    this.setGunColor(Color.yellow);
    this.setRadarColor(Color.gray);
    this.setScanColor(Color.white);
    this.setBulletColor(Color.white);
  }

  /**
   * Move robot to point specified by x, y. Collisions cancel movement to point.
   * 
   * @param toX X-coordinate of point
   * @param toY Y-coordinate of point
   */
  public void goToPoint(double toX, double toY) {
    double x = this.getX();
    double y = this.getY();
    double heading = this.getHeading();

    this.turnRight(getDegreesToTurnToFacePoint(x, y, heading, toX, toY));

    // Attempt to go to point.
    this.ahead(Math.sqrt(Math.pow(Math.abs(x - toX), 2) + Math.pow(Math.abs(y - toY), 2)));
  }

  /**
   * Get degrees needed to turn to face point specified by x, y. Collisions cancel movement to
   * point.
   * 
   * @param currX Current X coordinate of position
   * @param currY Current Y-coordinate of position
   * @param currHeading Current heading in degrees
   * @param toX X-coordinate of point
   * @param toY Y-coordinate of point
   * @return double Degrees to turn
   */
  public double getDegreesToTurnToFacePoint(double currX, double currY, double currHeading,
      double toX, double toY) {
    // Turn to face point.
    double angleGoal = Math.toDegrees(Math.atan((Math.abs(currY) - toY) / (Math.abs(currX) - toX)));
    // robot in 1st or 2nd quadrant relative to point.
    if (currX >= toX) {
      angleGoal = 270 - angleGoal - currHeading;
    }
    // Robot in 3rd or 4th quadrant relative to point.
    else {
      angleGoal = 90 - currHeading - angleGoal;
    }
    if (angleGoal % 360 == 0) {
      angleGoal = 0;
    }
    return angleGoal;
  }

  /**
   * Move robot to farthest point in a cardinal direction. If robot hits a wall prior to reaching
   * the destination, robot will turn and move straight to destination. Corners are considered NE,
   * SE, SW and NW. Sides are considered N, E, S, W.
   * 
   * @param cardinal Cardinal direction.
   */
  public void goToCardinal(String cardinal) {
    if ("N".equals(cardinal)) {
      this.turnToDegrees(0);
      this.ahead(this.battleFieldHeight);
    }
    else if ("E".equals(cardinal)) {
      this.turnToDegrees(90);
      this.ahead(this.battleFieldWidth);
    }
    else if ("S".equals(cardinal)) {
      this.turnToDegrees(180);
      this.ahead(this.battleFieldHeight);
    }
    else if ("W".equals(cardinal)) {
      this.turnToDegrees(270);
      this.ahead(this.battleFieldWidth);
    }
    else if ("NE".equals(cardinal)) {
      this.goToPoint(this.battleFieldWidth, this.battleFieldHeight);
      // If robot's body hit the wall and didn't make it to destination
      // try straightening out and continuing.
      if ((this.getY() + this.robotHeight) < this.battleFieldHeight) {
        this.turnToDegrees(0);
        this.ahead(this.battleFieldHeight);
      }
      else if ((this.getX() + this.robotWidth) < this.battleFieldWidth) {
        this.turnToDegrees(90);
        this.ahead(this.battleFieldWidth);
      }
    }
    else if ("SE".equals(cardinal)) {
      this.goToPoint(this.battleFieldWidth, 0);
      // If robot's body hit the wall and didn't make it to destination
      // try straightening out and continuing.
      if ((this.getY() + this.robotHeight) > this.robotHeight) {
        this.turnToDegrees(180);
        this.ahead(this.battleFieldHeight);
      }
      else if ((this.getX() + this.robotWidth) < this.battleFieldWidth) {
        this.turnToDegrees(90);
        this.ahead(this.battleFieldWidth);
      }
    }
    else if ("SW".equals(cardinal)) {
      this.goToPoint(0, 0);
      // If robot's body hit the wall and didn't make it to destination
      // try straightening out and continuing.
      if ((this.getY() + this.robotHeight) > this.robotHeight) {
        this.turnToDegrees(180);
        this.ahead(this.battleFieldHeight);
      }
      else if ((this.getX() + this.robotWidth) > this.robotWidth) {
        this.turnToDegrees(270);
        this.ahead(this.battleFieldWidth);
      }
    }
    else if ("NW".equals(cardinal)) {
      this.goToPoint(0, this.battleFieldHeight);
      // If robot's body hit the wall and didn't make it to destination
      // try straightening out and continuing.
      if ((this.getY() + this.robotHeight) < this.battleFieldWidth) {
        this.turnToDegrees(0);
        this.ahead(this.battleFieldHeight);
      }
      else if ((this.getX() + this.robotWidth) > this.robotWidth) {
        this.turnToDegrees(270);
        this.ahead(this.battleFieldWidth);
      }
    }
  }

  /**
   * Turn to face specified degree.
   * 
   * @param toDegrees Degree value to face
   */
  public void turnToDegrees(double toDegrees) {
    double heading = this.getHeading();

    if (toDegrees < 0 || toDegrees >= 360 || toDegrees == heading) {
      return;
    }

    this.turnRight(getDegreesToTurnToDegrees(heading, toDegrees));
  }

  /**
   * Turn to face specified degree.
   * 
   * @param heading Current heading in degrees
   * @param toDegrees Degree value to face
   * @return double Degrees to turn
   */
  public double getDegreesToTurnToDegrees(double heading, double toDegrees) {
    double a = 0;
    double b = 0;
    double changeDegrees = 0;

    if (toDegrees < 0 || toDegrees >= 360 || toDegrees == heading) {
      return 0;
    }

    // Get closest path to turn.
    if (toDegrees > heading) {
      a = toDegrees - heading;
      b = 360 - toDegrees + heading;
      if (a < b) {
        changeDegrees = a;
      }
      else {
        changeDegrees = -1 * b;
      }
    }
    else {
      a = heading - toDegrees;
      b = 360 - heading + toDegrees;
      if (a < b) {
        changeDegrees = -1 * a;
      }
      else {
        changeDegrees = b;
      }
    }

    return changeDegrees;
  }

  /**
   * Align body of robot to gun's current position. In other words, turn the robot to be facing the
   * current direction of the gun.
   */
  public void alignBodyToGun() {
    this.setAdjustGunForRobotTurn(true);
    this.turnRight(this.getDegreesToRealignGun(this.getGunHeading(), this.getHeading()));
    this.setAdjustGunForRobotTurn(false);
  }

  /**
   * Turn to face specified degree.
   * 
   * @param gunHeading Current heading of gun
   * @param bodyHeading Current heading of body
   * @return double Degrees to turn
   */
  public double getDegreesToRealignGun(double gunHeading, double bodyHeading) {
    return gunHeading - bodyHeading;
  }
}
